#pragma once

template <typename T>
struct hMatrix;
template <typename T>
struct hMatrixRow
{
	hMatrix<T>* _pOwner = NULL;
	size_t _row = 0;
	std::vector<T> _data;
	std::multimap<size_t, size_t>::const_iterator _colNumIt;

	void resize(size_t col);
	size_t size() const { return _data.size(); }

	T& operator[](size_t col);
	const T& operator[](size_t col) const;
};

template <typename T>
struct hMatrix
{
	struct FillOsParam;
	friend struct hMatrix::FillOsParam;

	std::multimap<size_t, size_t> _rowColNum;
	std::vector<hMatrixRow<T>> _data;

	hMatrix(const char* str = NULL);

	size_t row() const { return _data.size(); }
	size_t col() const;
	void pushBack(std::vector<T>* pData = NULL);
	void popBack();
	void resize(size_t row, size_t col);
	bool empty() const { return _data.empty(); }
	size_t size() const { return _data.size(); }
	bool convert(const char* str, size_t len);
	std::ostream& fillOs(std::ostream& os) const;

	hMatrixRow<T>& operator[](size_t row);
	const hMatrixRow<T>& operator[](size_t row) const;
};

template <typename T>
struct hMatrix<T>::FillOsParam
{
	size_t _rowNumMax = 0;
	size_t _colNumMax = 0;
	size_t _formatNum = 1;
	std::string _defaultT = std::to_string(T());
	std::map<size_t, size_t> _colWidthMax;
	std::map<size_t, std::map<size_t, std::string>> _dataStr;

	void fillData(const hMatrix<T>& m);
	std::ostream& fillOs(std::ostream& os);
};

template <typename T>
void hMatrixRow<T>::resize(size_t col)
{
	if (col == _data.size())
		return;

	_data.resize(col);
	_pOwner->_rowColNum.erase(_colNumIt);
	_colNumIt = _pOwner->_rowColNum.insert(std::make_pair(_data.size(), _row));
}

template <typename T>
T& hMatrixRow<T>::operator[](size_t col)
{
	if (col < _data.size())
		return _data[col];

	for (size_t i = _data.size(); i <= col; ++i)
		_data.push_back(T());

	_pOwner->_rowColNum.erase(_colNumIt);
	_colNumIt = _pOwner->_rowColNum.insert(std::make_pair(_data.size(), _row));
	return _data[col];
}

template <typename T>
const T& hMatrixRow<T>::operator[](size_t col) const
{
	assert(col < _data.size());
	return _data[col];
}

template <typename T>
hMatrix<T>::hMatrix(const char* str)
{
	if (str)
		convert(str, 0);
}

template <typename T>
size_t hMatrix<T>::col() const
{
	auto it = _rowColNum.rbegin();
	if (it == _rowColNum.rend())
		return 0;

	return it->first;
}

template <typename T>
void hMatrix<T>::pushBack(std::vector<T>* pData)
{
	hMatrixRow<T> row;
	row._pOwner = this;
	row._row = _data.size();
	_data.push_back(row);
	if (pData)
		_data.back()._data = *pData;

	_data.back()._colNumIt = _rowColNum.insert(std::make_pair(_data.back()._data.size(), row._row));
}

template <typename T>
void hMatrix<T>::popBack()
{
	if (_data.empty())
		return;

	_rowColNum.erase(_data.back()._colNumIt);
	_data.pop_back();
}

template <typename T>
void hMatrix<T>::resize(size_t row, size_t col)
{
	switch ((row <=> _data.size())._Value)
	{
	case std::strong_ordering::equal._Value:
		break;
	case std::strong_ordering::less._Value:
		do popBack(); while (row < _data.size());
		break;
	case std::strong_ordering::greater._Value:
		do pushBack(); while (row > _data.size());
		break;
	default:
		return;
	}

	for (auto& colDt : _data)
		colDt.resize(col);
}

template <typename T>
bool hMatrix<T>::convert(const char* str, size_t len)
{
	_data.clear();
	_rowColNum.clear();
	if (!str)
		return true;

	uint8_t step = 0;
	size_t begPos = 0;
	size_t endPos = 0;
	std::vector<T> tmp;
	while (true)
	{
		switch (step)
		{
		case 0:
			switch (str[begPos])
			{
			case 0:
				return true;
			case ' ':
				++begPos;
				break;
			case ',':
				++begPos;
				pushBack(&tmp);
				tmp.clear();
				break;
			default:
				step = 1;
				endPos = begPos + 1;
				break;
			}
			break;
		case 1:
			switch (str[endPos])
			{
			case 0:
				tmp.push_back(T());
				std::from_chars(str + begPos, str + endPos, tmp.back());
				pushBack(&tmp);
				return true;
			case ' ':
				tmp.push_back(T());
				std::from_chars(str + begPos, str + endPos, tmp.back());
				step = 0;
				begPos = endPos + 1;
				break;
			case ',':
				tmp.push_back(T());
				std::from_chars(str + begPos, str + endPos, tmp.back());
				pushBack(&tmp);
				tmp.clear();

				step = 0;
				begPos = endPos + 1;
				break;
			default:
				++endPos;
				break;
			}
			break;
		default:
			return true;
		}

	}

	return true;
}

template <typename T>
std::ostream& hMatrix<T>::fillOs(std::ostream& os) const
{
	FillOsParam param;
	param.fillData(*this);
	param.fillOs(os);

	return os;
}

template <typename T>
hMatrixRow<T>& hMatrix<T>::operator[](size_t row)
{
	if (row >= _data.size())
	{
		for (size_t i = _data.size(); i <= row; ++i)
			pushBack();
	}

	return _data[row];
}

template <typename T>
const hMatrixRow<T>& hMatrix<T>::operator[](size_t row) const
{
	assert(row < _data.size());
	return _data[row];
}

template <typename T>
std::ostream& operator<<(std::ostream& os, const hMatrix<T>& data)
{
	return data.fillOs(os);
}

template <typename T>
hMatrix<T> operator*(const hMatrix<T>& lhs, const hMatrix<T>& rhs)
{
	hMatrix<T> tmp;
	if (lhs.empty() || rhs.empty())
		return tmp;

	for (size_t i = 0; i < lhs.row(); ++i)
	{
		const hMatrixRow<T>& lhsRow = lhs[i];
		for (size_t j = 0; j < rhs.col(); ++j)
		{
			for (size_t k = 0; k < lhsRow.size() && k < rhs.size(); ++k)
			{
				const hMatrixRow<T>& rhsRow = rhs[k];
				if (j < rhsRow.size())
					tmp[i][j] += lhsRow[k] * rhsRow[j];
			}
		}
	}

	return tmp;
}

template <typename T>
void hMatrix<T>::FillOsParam::fillData(const hMatrix<T>& m)
{
	size_t i = 0, j = 0;
	_rowNumMax = m._data.size();
	if (m._rowColNum.rbegin() != m._rowColNum.rend())
		_colNumMax = m._rowColNum.rbegin()->first;
	for (size_t i = 0; i < _rowNumMax; ++i)
	{
		auto& col = m._data[i];
		for (size_t j = 0; j < col._data.size(); ++j)
		{
			std::string& tStr = _dataStr[i][j];
			tStr = std::to_string(col._data[j]);
			_colWidthMax[j] = std::max(_colWidthMax[j], tStr.size());
		}
	}
}

template <typename T>
std::ostream& hMatrix<T>::FillOsParam::fillOs(std::ostream& os)
{
	size_t i = 0, j = 0;
	for (size_t i = 0; i < _rowNumMax; ++i)
	{
		auto printBracket = [&]()
		{
			if (i == 0 || i + 1 == _rowNumMax) os << '+';
			else os << '|';
		};

		printBracket();
		auto& colStr = _dataStr[i];
		for (size_t j = 0; j < _colNumMax; ++j)
		{
			auto pStr = &colStr[j];
			size_t colMax = _colWidthMax[j];
			if (pStr->empty())
				pStr = &_defaultT;

			os << std::string(colMax - pStr->size(), ' ');
			os << *pStr;
			if (j + 1 != _colNumMax)
				os << ' ';
		}
		printBracket();
		if (i + 1 != _rowNumMax)
			os << '\n';
	}

	return os;
}